package com.baizhi.aspect;

import com.baizhi.annotation.LogAnnotation;
import com.baizhi.dao.CmfzLogDao;
import com.baizhi.entity.CmfzAdmin;
import com.baizhi.entity.CmfzLog;
import com.baizhi.util.IPKit;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Date;

/*
 * aspect包是个切面aop包  专门写aop类
 * 下面的LogAspect是 日志切面类  1、定义切入点信息 2、定义增强内容（一是采集日志信息  二是数据添加到数据库）
 * */
@Aspect
@Configuration
public class LogAspect {
    @Autowired
    private CmfzLogDao cmfzLogDao;//装配注入日志的dao   目的是这里的切面方法的增强内容就是要想日志表中添加记录
    /*
     * 这是一个日志的后置通知方法  目的是记录管理员操作的日志
     * 第一、切入点这里不再切方法  这里切我们的自定义注解
     *          这样所有的被自定义注解标注的方法都会 被aop
     * */
    @After("@annotation(com.baizhi.annotation.LogAnnotation)")
    public void logAfter(JoinPoint joinPoint){
        /*
         * 该方法：就是实现增强的代码  这里具体内容分两步
         * 第一、采集日志信息  封装到日志实体类对象中
         * 第二、将日志数据  添加到数据库中
         * */
        //1、采集日志信息
        /*需要采集的信息：
            a、logId 自增  不用采集
            b、操作的管理员amdinId   从session中获取   但session怎么获取呢？  用request
            有一个API：在Spring项目中通过它可以在代码任何位置获取request，如下：
         ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
            c、操作地点log_ip  获取方法也是用request.getRemoteAddr()获取一个远程IP
                        但一般不用上面的  都是采用工具类获取一个远程IP
            d、操作的时间log_date   直接new Date()
            e、操作内容log_content
            f、操作类型log_type
            最后这两步的难点是：不同的方法操作内容不一样 如：
            如果是查询上师的方法 获取到的操作内容应该是：查询上师数据 类型是：查询
            如果是删除图片的方法 获取到的操作内容应该是：删除图片数据 类型是：删除

            解决方案：通过自定义注解
            获取注解的值
           因为每一个方法上的注解内容都是不一样的 所以获取到内容和类型也不一样的
           步骤：
           1.自定义注解（定义两个属性代表操作内容和操作类型），用在要切入的方法上（一般用在Controller上）
              所以这个aop类的切入点配置成了 注解切入方式@After("@annotation(com.baizhi.annotation.LogAnnotation)")
           2.在增强中获取被切入的方法上的注解  利用JoinPoint对象  所以要给该aop方法添加一个JoinPoint类型的形参
           3.通过JoinPoint形参获取注解的值
         * */
        CmfzLog cmfzLog = new CmfzLog();
        //b、封装管理员ID
        // 首先 利用RequestContextHolder类获取request对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpSession session = request.getSession();//获取session对象
        CmfzAdmin admin = (CmfzAdmin) session.getAttribute("login");//从session中获取登陆对象
        //然后 条件判断  封装管理员id到日志对象
        if(admin!=null){
            cmfzLog.setAdminId(admin.getId());
        }else {
            cmfzLog.setAdminId(0);
        }
        //c、封装操作日期 log_date  new Date()获取当前时间
        cmfzLog.setLogDate(new Date());
        //d、封装操作地址 log_ip  也要通过request对象  上面已经获得了  这里调用工具类获取IP
        String ip = IPKit.getIpAddrByRequest(request);
        cmfzLog.setLogIp(ip);
        //e、封装操作内容log_content和操作类型log_type
        /*  重点：记住joinPoint这个切入点对象有这个功能（获取到方法、注解、类、属性）
            在增强中可以通过切入点对象  joinPoint 获取到方法 获取注解  获取类 获取到属性
            通过切入点对象 joinPoint 获取到方法签名对象 注意强转类型(MethodSignature) joinPoint.getSignature();
            通过方法签名对象获取到方法对象  signature.getMethod()
            通过方法对象获取方法上的注解  method.getAnnotation(LogAnnotation.class); 参数为 注解的类对象
        */
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取方法签名对象
        Method method = signature.getMethod();//获取方法对象
        LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);//获取注解对象 形参是注解的类对象
        //然后通过自定义注解  获取其内的属性值（即标注在方法上的注解内容）
        String content = annotation.content();
        String type = annotation.type();
        //封装进日志对象
        cmfzLog.setLogContent(content);
        cmfzLog.setLogType(type);
        //2、将日志数据 添加到数据库
        cmfzLogDao.insert(cmfzLog);
    }
}
